![]() ![]() |
はじめにMac OS 9 と Mac OS X のスレッディングの特性が異なっていても、驚くには値しないでしょう。Mac OS X(Carbon の一部として)は Mac OS 9 のスレッディング API をサポートしてはいますが、それにはまったく異なるコア OS を使っています。それにより、このコア OS がスレッディング API の動作に微妙な変化をもたらしています。 このテクニカルノートでは両方のプラットフォームにおけるスレッディングアーキテクチャの基本について説明します。基本的な設計思想がわかると、それぞれのプラットフォームのスレッドの扱いにおける微妙な違いを理解していただけるでしょう。最終的な目的は、スレッド化されたアプリケーションを各プラットフォーム上の動作に適合させるための調整をしやすくすることです。それがアプリケーションやシステム上のその他のアプリケーションの性能向上につながります。
このテクニカルノートでは、特に Mac OS 9.1 とMac OS X 10.0.x のスレッディングアーキテクチャについて説明します。従来の Mac OS の旧バージョンとは異なっており、Mac OS X の将来的なバージョンでも異なる可能性があります。Mac OS X は急速に進化しつつあるシステムです。このドキュメントは、Mac OS X のスレッディング機能について考えるときのガイドとして利用していただき、このプラットフォームにおけるスレッディングの最終的な定義であるとは考えないでください。 Mac OS 9 のスレッディングこのセクションでは Mac OS 9 のスレッディングについて説明します。Mac OS 9 には 2 つのスレッディング API があります。
Mac OS 9 のスレッディングを理解するには、これらのスレッディング API に加えて、Process Manager に実装されている Mac OS 9 のプロセススケジューリングを忘れてはなりません。 これらの API それぞれのプログラミングに関する技術文書については、このテクニカルノートの巻末にある
参考文献
のセクションを参照してください。
MP タスクのない Mac OS 9一時的に MP タスクを除外した場合、Mac OS 9 のスレッディングアーキテクチャを図 1 に示します。
このアーキテクチャでは、スレッディングはすべて協調的に行われます。プロセスが WaitNextEvent のような yield 関数を呼び出したとき、各プロセスは Process Manager によってラウンドロビン方式でスケジュールされます。それぞれのプロセス内に協調的にスケジュールされたスレッドのセットがあって、プロセスが YieldToAnyThread(あるいは YieldToThread)を呼び出すと Thread Manager によってスケジュールされます。 このダイアグラムから以下の結論が導けます。
Mac OS 9 のスレッディングで重要な特徴の 1 つに、Process Manager のプロセスが非常に不経済なことがあげられます。大量のリソース(プロセスにはサイズ固定のメモリパーティションがあります)を消費する上に、歴史的な理由からプロセス間のコンテキストスイッチに非常に時間がかかるのです。 MP タスクのある Mac OS 9MP タスクを考慮に入れると、Mac OS 9 のスレッディングアーキテクチャは、図 2 に示すように多少複雑になります。
協調的な環境全体が 1 つの MP タスク内で動いています。このタスクはブルータスクとして知られています。Process Manager のプロセスと Thread Manager のスレッドはすべて、ブルータスクによって実行されます。システムやアプリケーションによって作成される、その他の MP タスクは個別のエンティティとして実行されます。ナノカーネルはタスクがプリエンプティブな形でプロセッサ(あるいはマルチプロセッサ)上で動くようスケジュールします
このアーキテクチャに関して、以下の点に注意してください。
Mac OS X のスレッディングこのセクションでは Mac OS X のスレッディングについて記述します。5 つの異なる API が提供されています。
「 Carbon Specification 」では、Mac OS X で MP タスクや Thread Manager のコードを Carbon に移植する際の問題について説明しています。 Mac OS X におけるさまざまなスレッディング API について考えるとき、それらをレイヤ階層に並べてみると便利です。たとえば、各 MP タスクは pthread のすぐ上のレイヤにあり、各 pthread は Mach スレッドのすぐ上のレイヤにあります。この階層を図 3 に示しています。
これらの API それぞれのプログラミングに関する技術文書については、このテクニカルノートの巻末にある 参考文献 のセクションを参照してください。
スレッド API の選択公開されているスレッディング API には 5 種類あり、どれを使うか決めかねることもあるでしょう。ここに一般的なガイドラインを示します。
Mac OS 9 と比べ、Mac OS X ではすべてのスレッドがだいたい同じコストになります。pthread や MP タスク、協調的なスレッドを
NSThread としてラッピングする際のオーバーヘッドは、Mach ベースのスレッドのオーバーヘッドと比べて小さくなります。これはまた、協調的なスレッド(他のタイプのスレッドに比べて)は、Mac
OS 9 上にあるときと比べるとより不経済だということも意味しています。Mac OS X におけるスレッドのコストの詳細については『
Inside Mac OS X: Performance
』を参照してください。
スレッドのコンテキストスイッチに要する時間は、どのスレッディング API を選択するかで異なります。何気ないコンテキストスイッチやカーネル内の I/O ブロックでは、そのスレッドをどのように作成してもオーバーヘッドは同じになります。その他のコンテキストスイッチ(たとえば、セマフォを待つなど)にかかるオーバーヘッドは選択した API によりますが、ハイレベル API は通常より時間がかかります。 Mac OS X のスレッドスケジューリングこれまでの説明してきたスレッド階層に関する結論の 1 つとして、究極的にはすべてのスレッドは Mach スレッドで表現できるということです。Mach に関する限り、すべてのスレッドは同等です。Mach スケジューラは、実行すべきスレッドを選択するとき必ず自らの スケジューリングポリシー に従います。Mach スレッドが pthread なのか、あるいは MP タスクや協調的スレッドなのかなどは一切関係ありません。したがって、図 4 に示すように、Mach スケジューリングは 1 つの均一な輪として表現できます。
協調的スレッドが実装されている方法を理解するには、この図を横から見て、基調となる Mach スレッドの上にどのようにアブストラクションがレイヤとして存在しているかを確かめる必要があります。図
5 にそれを示します。各アブストラクションの一番下のレイヤは Mach スレッドで、これは実際にはカーネルがスケジュールしています。Mach スレッドの中には生の形のままで存在しているものもあります(このようなスレッドは一般的に
カーネル
内部で実行されるように作成されています)。ユーザスペース Mach スレッドは、すべて上のレイヤに pthread を載せています。Cocoa
アプリケーションによって作成されたスレッドは上部レイヤに NSThread を載せています。Carbon は Carbon 自身のためにも、あるいは上位レベルのスレッディング
API を実装するためにもスレッドを作成することができます。スレッドが
Carbon によって内部で使用される
ために作成される場合、pthread のとても単純なラッパーが実装されます。Carbon アプリケーションの命令によって Carbon がスレッドを作成された場合、それも
pthread になりますが、Thread Manager スレッドか MP タスクがその上のレイヤに存在します。
Carbon は単一プロセス内の Thread Manager スレッドがすべて協調的にスケジュールされていることを保証します。各プロセスは、プロセス内の協調的スレッド間で渡される特別な同期トークン(トークンは Mach メッセージとして実装されています)を持っています。協調的スレッドがトークンを持っている場合は、そのスレッドは実行可能になります。持っていない場合は、トークンを待ってブロックされます。トークンを持った協調的スレッドが YieldToAnyThread を呼び出すと、Carbon は次の協調的スレッドを選択してトークンをそのスレッドに渡します。新しいスレッドの実行が開始され、はじめのスレッドはそのトークンを待ってブロックされます。 このトークン渡しの配列は図 5 に赤いラインで示されています。 Mach スケジューリングポリシーと優先順位Mach スレッドのポリシーは、そのスレッドをスケジュールするために使用されるアルゴリズムを制御します。Mac OS
X で推奨されるスレッドポリシーは以下の通りです。
各ポリシーにはそれぞれいくつかポリシーパラメータがあって、スレッドがそのポリシーの内部でどう動作するかを制御しています。それらのパラメータは、スレッドプライオリティの一般的なアイディアにほぼ相当しています。たとえば、優先順位ポリシーを使ったスレッドは、同一タスク内にある他のスレッドに比べてそのスレッドのプライオリティを制御する「重要性」パラメータを持っています。しかし、このきわめて単純化した等価の図式も、ずっと複雑な時間制約ポリシーの前では崩れ去ります。ここではポリシーパラメータは実際にはいくつかのリアルタイム値なのです。 各スレッディング API はそれぞれの考えに基づいてスレッドの優先順位を決めています。たとえば、MP タスクにはタスクウエイトという概念(MPSetTaskWeight によって定義されます)があり、pthread にはスレッドスケジューリングパラメータ(pthread_setschedparam によってセットされます)があります。各スレッディング API はそれぞれの優先順位の概念に従って、基礎となる Mach ポリシーやポリシーパラメータにマップします。一般にこれらのアルゴリズムは未公開で、今後変更される可能性もありますが、 Darwin のソースコード をお読みいただくと pthread がどのようにしてこのマッピングを行っているのか、より詳しく学ぶことができます。 Mac OS で推奨されるスレッドポリシーの詳細については、<mach/thread_policy.h> を参照してください。Mac OS X は、古くて未使用状態になっているスレッドポリシーの一部もサポートしています。<mach/policy.h> で定義されていますのでお読みください。 Mac OS X スレッドのいろいろこのセクションでは、Mac OS X のスレッディング関連のさまざまなトピックについて説明します。 メインスレッドメインスレッドはすべてのスレッディング API に有効なスレッドです。たとえば、メインスレッドから MPTaskIsPreemptive(MP API) と ThreadCurrentStackSpace(Thread Manager の API) を呼び出せます。これは 図 3 に示す階層の解釈からは厳密には外れますが、広く実用的だと見なされています。 Carbon と割り込み従来の Mac OS は割り込み時にもたらされる I/O 完了ルーチンを使った非同期 I/O を多用しています。しかし、Mac OS
X のコア OS はコールバックベースの非同期 I/O モデルをサポートしておらず、ユーザスペースコードは割り込み時には実行されません。Carbon
はプリエンプティブスレッドを使った非同期 I/O 完了ルーチンをシミュレートします。 たとえば、非同期の File Manager 要求をすると、Carbon は単にその要求を内部キューに置き、その非同期ファイル I/O スレッドのスリープを解除します。そのスレッドがキューから最初の項目を受け取って同期的に実行し、その完了ルーチンを呼び出します。キューに項目がなくなると、そのスレッドはスリープ状態になります。結局、基礎となるコア OS には非同期ファイルシステム API がなくても、非同期的に操作されているように見えるわけです。 Carbon は、File Manager や Time Manager、Deferred Task Manager、Open Transport
など、多くの異なった割り込み時コールバックベースの API でこれと似たテクニックを使っています。各サブシステムはコールバックを行うために自らの pthread
を作成しています。 この設計により、いくつもの興味深い結果が生まれました。
ミックスマッチ使用可能なスレッディング API すべてがそろうと、いずれかの時点でスレッドとその API を混ぜて使いたくなることもあります。たとえば
MP タスク上で Mach スレッド API を呼び出したい、pthread 上で MP を呼び出したい場合です(
図 3
に示したレイヤ階層の仮定でいえば)。1 つのスレッドが他のスレッドの上のレイヤにあれば、一般的にはハイレベルスレッドでローレベル操作を行うことは可能です。たとえば、ほとんどの
pthread 呼び出しは MP タスク上で行っても安全です。しかしこれにはいくつか考慮すべき問題があります。 最初の問題は、どのようにしてローレベルスレッドへの参照を得るかということです。pthread API だけが下位にあるスレッドへの参照を得る明確なメカニズムを提供できます(pthread_mach_thread_np が pthread に Mach スレッドを返します)。他のスレッド API はどれもこの機能を明確にサポートしていません。ローレベルスレッドへの参照を得る唯一の方法は、ハイレベルスレッドのコンテキスト内にいる間に、ローレベルの「自己」(あるいは「カレントスレッド」)API を呼び出すことです。たとえば、ある MP タスクの pthread を検索するには、その MP タスク内から pthread_self 呼び出しをすればいいのです。 次に、ローレベル操作が実際にハイレベルスレッドで機能するかということです。一般的には、ほとんどの pthread API がより上位レベルのスレッド
API によって作成された pthread API で安全です。一方、pthread(および、あらゆるハイレベル API によって作られたスレッド上)で
Mach スレッド呼び出しをするときには十分注意を払ってください。一般に、pthread 上で Mach スレッド呼び出しをするのは適切ではありません。例外はスレッド情報の取得や、スレッド例外ハンドラのセットアップ、終了通知などのための
Mach API で、これらは作成方法に関係なくどんな Mach スレッドでも機能することになっています。 最後に、pthread の面ではハイレベルのスレッドのほとんどが実装されたとして、ハイレベルスレッド API を任意の pthread で使用することは安全でしょうか。たとえば、pthread 上で MP API ルーチンを呼び出しても大丈夫でしょうか。このアプローチでは確実にスレッドのレイヤ階層を壊してしまうので推奨できません。しかし 1 つ特記すべき、そして役に立つ例外があります。それは pthread 上で MP API 同期呼び出し (MPNotify/WaitOnQueue、MPSignal/WaitOnSemaphore、 MPEnter/ExitCriticalRegion、およびMPSet/WaitForEvent) をしても安全だということです。おかげでメインスレッドと Carbon の「割り込み」 とを同期させるために MP API ルーチンを使えるのでとても便利です。 先頭に戻るMac OS X カーネルスレッディング従来の BSD ベースのシステムではカーネルは上下半分に分かれていました。上半分は特定のカーネル呼び出しを行ったユーザスレッドによって実行されます。I/O の完了や、共有カーネルリソースへのアクセスを待ったりしている間、ブロック(スリープ)することができます。下半分はハードウェア割り込みの結果として実行されます。割り込みはブロックしてはいけません。上半分は割り込みを無効にすることで下半分と同期しています。 McKusick, et al. は著書でこの設計に関して詳細に説明しています。 Mac OS X は BSD カーネルのソースコードの大部分を使用していますが、従来の BSD システムとは違います。その Mach
の土台が BSD カーネルの同期メカニズムの改訂を要求するのです。たとえば、Mach はマルチプロセッサシステムをサポートしていて、割り込みを無効にしても、もはや同期を保証するに十分ではありません。 Mac OS X の BSD カーネルの上半分は、まだカーネル呼び出しを行ったユーザスレッドのコンテキスト内で実行されています。しかし、下半分はもはやハードウェア割り込みコンテキスト内では実行されていません。ハードウェア割り込みは Mac OS X ではごく狭い範囲しか扱っていません。ハードウェア割り込みが起こると、単に IOKit ワークループスレッドのスリープ解除を行います。これらのワークループスレッドは、 カーネルスレッドです。つまり、カーネルが所有しているのであって、BSD プロセスではありません。ワークループスレッドは BSD カーネルの下半分の実行を行うエンティティです。BSD カーネルの同期アプローチ(割り込みを無効にするために splx 呼び出しを使うこと)は、もはや Mac OS X では効果がなく、BSD カーネルの上半分と下半分は異なるプロセッサ上の異なるスレッドで実行されることも可能であることを意味します。 この問題を解決するのがカーネルファンネルです。カーネルファンネルはカーネルの BSD 部分の内部で一度に複数のスレッドが実行されるのを防ぐミューテックスです。それぞれのスレッドがカーネルの
BSD 部分に入るときにカーネルファンネルを取得し、出るときには解放します。さらに、スレッドがファンネルをホールドしている間にブロック(スリープ)した場合、自動的にファンネルを手放し、他のスレッドがカーネルに入れるようにします。 Mac OS X 10.0.x はスプリットファンネルを実装しています。カーネルのネットワーキングの部分に 1 つのファンネルがあり、カーネルの他の BSD 部分(ファイルシステム、プロセス管理、デバイス管理など)にもファンネルが 1 つあります。このように分離していることにより、ディスクとネットワークの両方を使うタスクのパフォーマンスが著しく向上する結果となりました。Mac OS X の将来的なバージョンではさらに多くのファンネルを使うことも考えられ、カーネルリエントラントが改善され、さらにすぐれたパフォーマンスが期待できるかもしれません。 Mach スケジューラに関する限り、BSD カーネル内で実行されているスレッドはほかの Mach スレッドと同じです。このために起こってくる興味深い結果の 1 つに、カーネルプリエンプションがあります。優先度の高いスレッドのスリープが解除されると、BSD カーネル内で実行されているスレッドをプリエンプトするのです。これは、その優先度の高いスレッドがカーネルスレッドなのか、ユーザスレッドなのかに関係なく行われます。優先度の高いスレッドがカーネルファンネルを取得しようとしない限り(つまり BSD システム呼び出しをしないことになります)、カーネルの BSD 部分のリエントラントが制限されていてもその役目を果たします。この設計によって、Mac OS X は非常に応答性の高いオーディオプレイバックエンジンのような、リアルタイムコンポーネントが要求するリアルタイムの目標を満たすことができるのです。 要約Mac OS X には 5 つのスレッディング API があるので複雑に思われるかもしれませんが、実際には Mac OS 9 のスレッディングよりもシンプルです。以下の基本的な概念さえ理解していただけばいいのです。
参考文献Inside Mac OS X: System Overview DTS Q&A PS 06 Yielding Time Without Getting Events -- この Q&A では、従来の Mac OS において、なぜユーザインタフェースイベントを扱わずに他のプロセスへの明け渡しができないのかを説明しています。 DTS Q&A 1061 RunApplicationEventLoop and Thread Manager -- この Q&A では、協調的なスレッドをRunApplicationEventLoop ベースの Carbon アプリケーションに統合させる際の問題について説明しています。 DTS Technote 1104 Interrupt-Safe Routines DTS Technote 2006 MP-Safe Routines Carbon Specification -- MP タスクや Thread Manager コードを Mac OS X の Carbon に移植する際に直面する可能性のある問題について記述しています。 Mach 3 Kernel Principles and Interfaces -- これらの書類は古い内容も含んでいますが、今でも Mach 3 カーネルやそのプログラミング API の入門書としては価値があります。 Darwin -- Mac OS X のカーネル(CVS モジュール「xnu」)と pthread ライブラリ(CVS モジュール「Libc」)のソースコードがアップルのオープンソースの試みの 1 つとして入手できます。 The Open Group's Single
UNIX Specification
-- ここには pthread API の参考文献が含まれています。 David R Butenhof , Programming with POSIX Threads
, Addison-Wesley, 1997, ISBN: 0201633922 -- UNIX システムの pthread プログラミングのいい入門書です。 NSThread Class -- NSThread クラスの参考文献。 Inside
Macintosh: Processes
-- Process Manager とその API について記述しています。 Inside Macintosh: Macintosh Toolbox Essentials -- 本書には Event Manager についての章があり、きわめて重要な WaitNextEvent 呼び出しについて記述しています。 Inside Macintosh: Thread Manager -- Mac OS 9 の Thread Manager について説明しています。
Inside Carbon: Thread Manager
-- Carbon Thread Manager について説明しています。 Adding Multitasking Capability to Applications Using Multiprocessing Services -- Mac OS 9 の MP タスク API について説明しています。 Adding Multitasking Capability to Applications Using Multiprocessing Services -- Carbon の MP タスク API について説明しています。 Inside Mac OS X: Performance -- Mac OS X のスレッドのメモリコストに関する情報が含まれています。 Marshall Kirk McKusick, et al., The Design and Implementation of the 4.4BSD Operating System, Addison-Wesley, 1996, ISBN: 0201549794 |